namespace FixedPointMath
{
    public static class MathFP
    {
        /// <summary>
        /// 取较大值
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Number Max(Number a, Number b)
        {
            return (a < b) ? b : a;
        }

        /// <summary>
        /// 取较小值
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Number Min(Number a, Number b)
        {
            return (a < b) ? a : b;
        }

        /// <summary>
        /// 取绝对值
        /// </summary>
        /// <param name="num"></param>
        /// <returns></returns>
        public static Number Abs(Number num)
        {
            return num > Number.Zero ? num : -num;
        }

        /// <summary>
        /// 求平方根
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Number Sqrt(Number v)
        {
            return v.Sqrt();
        }

        /// <summary>
        /// 区间限制
        /// </summary>
        /// <param name="v"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        public static Number Clamp(Number v, Number min, Number max)
        {
            return v < min ? min : (v > max ? max : v);
        }

        /// <summary>
        /// 线性插值
        /// </summary>
        /// <param name="src">起始值</param>
        /// <param name="dest">目标值</param>
        /// <param name="ratio">差值系数. 0 在起点; 1 在终点</param>
        /// <returns></returns>
        public static Number Lerp(Number src, Number dest, Number ratio)
        {
            return src + (dest - src) * ratio;
        }
        public static Vector2 Lerp(Vector2 src, Vector2 dest, Number ratio)
        {
            var delta = dest - src;
            return new Vector2(src.x + delta.x * ratio, src.y + delta.y * ratio);
        }
        public static Vector3 Lerp(Vector3 src, Vector3 dest, Number ratio)
        {
            var delta = dest - src;
            return new Vector3(src.x + delta.x * ratio, src.y + delta.y * ratio, src.z + delta.z * ratio);
        }

        #region 向量
        /// <summary>
        /// 向量点积
        /// https://blog.csdn.net/qq_27245709/article/details/70992729
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Number Dot(Vector2 a, Vector2 b)
        {
            return a.x * b.x + a.y * b.y;
        }

        /// <summary>
        /// 向量点积
        /// https://blog.csdn.net/qq_27245709/article/details/70992729
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Number Dot(Vector3 a, Vector3 b)
        {
            return a.x * b.x + a.y * b.y + a.z * b.z;
        }

        /// <summary>
        /// 向量叉积
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Vector3 Cross(Vector3 lhs, Vector3 rhs)
        {
            return new Vector3(lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x);
        }

        /// <summary>
        /// 向量a到向量b的夹角(角度)
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Number Angle(Vector2 lhs, Vector2 rhs)
        {
            var n = Dot(lhs, rhs) / (lhs.magnitude * rhs.magnitude);
            n = ((n >= Number.NN1) ? ((n <= Number.N1) ? n : Number.N1) : Number.NN1);
            return AcosDegree(n);
        }

        /// <summary>
        /// 向量a到向量b的夹角(角度)
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Number Angle(Vector3 lhs, Vector3 rhs)
        {
            var n = Dot(lhs, rhs) / (lhs.magnitude * rhs.magnitude);
            n = ((n >= Number.NN1) ? ((n <= Number.N1) ? n : Number.N1) : Number.NN1);
            return AcosDegree(n);
        }
        #endregion

        #region 三角函数
        /// <summary>
        /// acos: 返回cos的角度值
        /// [0, 180]
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Number AcosDegree(Number v)
        {
            return AcosLookup.Degree(v);
        }
        public static Number AcosRadin(Number v)
        {
            return AcosDegree(v) * Number.PI / Number.N180;
        }

        /// <summary>
        /// asin: 返回sin的角度值
        /// [-90,90]
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Number AsinDegree(Number v)
        {
            if (v > Number.Zero)
            {
                return Number.N90 - AcosDegree(v);
            }
            else
            {
                return AcosDegree(-v) - Number.N90;
            }
        }
        public static Number AsinRadin(Number v)
        {
            return AsinDegree(v) * Number.PI / Number.N180;
        }

        /// <summary>
        /// atan：返回tan的角度值[-180,180]
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Number AtanDegree(Number v)
        {
            return Atan2Degree(v, Number.N1);
        }
        public static Number AtanRadin(Number v)
        {
            return Atan2Radin(v, Number.N1);
        }

        /// <summary>
        /// atan: 返回tan的角度值[-180,180]
        /// </summary>
        /// <param name="y"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        public static Number Atan2Degree(Number y, Number x)
        {
            return Atan2Radin(y, x) * Number.R180 / Number.PI;
        }

        private static Number a1 = -46496.4749;// 1000000
        private static Number a2 = 159314.22;// 1000000
        private static Number a3 = 327622.764;// 1000000
        public static Number Atan2Radin(Number dy, Number dx)
        {
            Number ax = dx < Number.Zero ? -dx : dx;
            Number ay = dy < Number.Zero ? -dy : dy;
            Number a = MathFP.Min(ax, ay) / (MathFP.Max(ax, ay) + Number.Epsilon);
            Number s = a * a;
            Number r = ((a1 * s + a2) * s - a3) * s * a / Number.R1000000 + a;
            if (ay > ax)
            {
                r = Number.HalfPI - r;
            }
            if (dx < Number.Zero)
            {
                r = Number.PI - r;
            }
            if (dy < Number.Zero)
            {
                r = -r; // [-PI, +PI]
                //r = Number.DoublePI - r;    // [0, 2*PI]                   
            }
            return r;
        }

        /// <summary>
        /// 计算角度的sin值
        /// </summary>
        /// <param name="degree"></param>
        /// <returns></returns>
        public static Number SinDegree(Number degree)
        {
            return SinCosLookup.SinDegree(degree);
        }
        /// <summary>
        /// 计算弧度的sin值
        /// </summary>
        /// <param name="radin"></param>
        /// <returns></returns>
        public static Number SinRadin(Number radin)
        {
            return SinCosLookup.SinRadin(radin);
        }

        /// <summary>
        /// 计算角度的cos值
        /// </summary>
        /// <param name="degree"></param>
        /// <returns></returns>
        public static Number CosDegree(Number degree)
        {
            return SinCosLookup.CosDegree(degree);
        }
        /// <summary>
        /// 计算弧度的cos值
        /// </summary>
        /// <param name="radin"></param>
        /// <returns></returns>
        public static Number CosRadin(Number radin)
        {
            return SinCosLookup.CosRadin(radin);
        }

        /// <summary>
        /// 计算角度的tan值
        /// </summary>
        /// <param name="degree"></param>
        /// <returns></returns>
        public static Number TanDegree(Number degree)
        {
            return TanCotLookup.TanDegree(degree);
        }
        /// <summary>
        /// 计算弧度的tan值
        /// </summary>
        /// <param name="radin"></param>
        /// <returns></returns>
        public static Number TanRadin(Number radin)
        {
            return TanCotLookup.TanRadin(radin);
        }

        /// <summary>
        /// 计算角度的cotan值
        /// </summary>
        /// <param name="degree"></param>
        /// <returns></returns>
        public static Number CotanDegree(Number degree)
        {
            return TanCotLookup.CotanDegree(degree);
        }
        /// <summary>
        /// 计算弧度的cotan值
        /// </summary>
        /// <param name="radin"></param>
        /// <returns></returns>
        public static Number CotanRadin(Number radin)
        {
            return TanCotLookup.CotanRadin(radin);
        }
        #endregion

        //public static VInt3 Transform(ref VInt3 point, ref VInt3 axis_x, ref VInt3 axis_y, ref VInt3 axis_z, ref VInt3 trans)
        //{
        //    return new VInt3(MathFP.Divide(axis_x.x * point.x + axis_y.x * point.y + axis_z.x * point.z, 1000) + trans.x, MathFP.Divide(axis_x.y * point.x + axis_y.y * point.y + axis_z.y * point.z, 1000) + trans.y, MathFP.Divide(axis_x.z * point.x + axis_y.z * point.y + axis_z.z * point.z, 1000) + trans.z);
        //}

        //public static VInt3 Transform(VInt3 point, ref VInt3 axis_x, ref VInt3 axis_y, ref VInt3 axis_z, ref VInt3 trans)
        //{
        //    return new VInt3(MathFP.Divide(axis_x.x * point.x + axis_y.x * point.y + axis_z.x * point.z, 1000) + trans.x, MathFP.Divide(axis_x.y * point.x + axis_y.y * point.y + axis_z.y * point.z, 1000) + trans.y, MathFP.Divide(axis_x.z * point.x + axis_y.z * point.y + axis_z.z * point.z, 1000) + trans.z);
        //}

        //public static VInt3 Transform(ref VInt3 point, ref VInt3 axis_x, ref VInt3 axis_y, ref VInt3 axis_z, ref VInt3 trans, ref VInt3 scale)
        //{
        //    long num = (long)point.x * (long)scale.x;
        //    long num2 = (long)point.y * (long)scale.x;
        //    long num3 = (long)point.z * (long)scale.x;
        //    return new VInt3((int)MathFP.Divide((long)axis_x.x * num + (long)axis_y.x * num2 + (long)axis_z.x * num3, 1000000L) + trans.x, (int)MathFP.Divide((long)axis_x.y * num + (long)axis_y.y * num2 + (long)axis_z.y * num3, 1000000L) + trans.y, (int)MathFP.Divide((long)axis_x.z * num + (long)axis_y.z * num2 + (long)axis_z.z * num3, 1000000L) + trans.z);
        //}

        //public static VInt3 Transform(ref VInt3 point, ref VInt3 forward, ref VInt3 trans)
        //{
        //    VInt3 up = VInt3.up;
        //    VInt3 vInt = VInt3.Cross(VInt3.up, forward);
        //    return MathFP.Transform(ref point, ref vInt, ref up, ref forward, ref trans);
        //}

        //public static VInt3 Transform(VInt3 point, VInt3 forward, VInt3 trans)
        //{
        //    VInt3 up = VInt3.up;
        //    VInt3 vInt = VInt3.Cross(VInt3.up, forward);
        //    return MathFP.Transform(ref point, ref vInt, ref up, ref forward, ref trans);
        //}

        //public static VInt3 Transform(VInt3 point, VInt3 forward, VInt3 trans, VInt3 scale)
        //{
        //    VInt3 up = VInt3.up;
        //    VInt3 vInt = VInt3.Cross(VInt3.up, forward);
        //    return MathFP.Transform(ref point, ref vInt, ref up, ref forward, ref trans, ref scale);
        //}

        //public static int CeilPowerOfTwo(int x)
        //{
        //    x--;
        //    x |= x >> 1;
        //    x |= x >> 2;
        //    x |= x >> 4;
        //    x |= x >> 8;
        //    x |= x >> 16;
        //    x++;
        //    return x;
        //}

        //public static void SegvecToLinegen(ref VInt2 segSrc, ref VInt2 segVec, out long a, out long b, out long c)
        //{
        //    a = (long)segVec.y;
        //    b = (long)(-(long)segVec.x);
        //    c = (long)segVec.x * (long)segSrc.y - (long)segSrc.x * (long)segVec.y;
        //}

        //private static bool IsPointOnSegment(ref VInt2 segSrc, ref VInt2 segVec, long x, long y)
        //{
        //    long num = x - (long)segSrc.x;
        //    long num2 = y - (long)segSrc.y;
        //    return (long)segVec.x * num + (long)segVec.y * num2 >= 0L && num * num + num2 * num2 <= segVec.sqrMagnitudeLong;
        //}

        //public static bool IntersectSegment(ref VInt2 seg1Src, ref VInt2 seg1Vec, ref VInt2 seg2Src, ref VInt2 seg2Vec, out VInt2 interPoint)
        //{
        //    long num;
        //    long num2;
        //    long num3;
        //    MathFP.SegvecToLinegen(ref seg1Src, ref seg1Vec, out num, out num2, out num3);
        //    long num4;
        //    long num5;
        //    long num6;
        //    MathFP.SegvecToLinegen(ref seg2Src, ref seg2Vec, out num4, out num5, out num6);
        //    long num7 = num * num5 - num4 * num2;
        //    if (num7 != 0L)
        //    {
        //        long num8 = MathFP.Divide(num2 * num6 - num5 * num3, num7);
        //        long num9 = MathFP.Divide(num4 * num3 - num * num6, num7);
        //        bool result = MathFP.IsPointOnSegment(ref seg1Src, ref seg1Vec, num8, num9) && MathFP.IsPointOnSegment(ref seg2Src, ref seg2Vec, num8, num9);
        //        interPoint.x = (int)num8;
        //        interPoint.y = (int)num9;
        //        return result;
        //    }
        //    interPoint = VInt2.zero;
        //    return false;
        //}

        //public static bool PointInPolygon(ref VInt2 pnt, VInt2[] plg)
        //{
        //    if (plg == null || plg.Length < 3)
        //    {
        //        return false;
        //    }
        //    bool flag = false;
        //    int i = 0;
        //    int num = plg.Length - 1;
        //    while (i < plg.Length)
        //    {
        //        VInt2 vInt = plg[i];
        //        VInt2 vInt2 = plg[num];
        //        if ((vInt.y <= pnt.y && pnt.y < vInt2.y) || (vInt2.y <= pnt.y && pnt.y < vInt.y))
        //        {
        //            int num2 = vInt2.y - vInt.y;
        //            long num3 = (long)(pnt.y - vInt.y) * (long)(vInt2.x - vInt.x) - (long)(pnt.x - vInt.x) * (long)num2;
        //            if (num2 > 0)
        //            {
        //                if (num3 > 0L)
        //                {
        //                    flag = !flag;
        //                }
        //            }
        //            else if (num3 < 0L)
        //            {
        //                flag = !flag;
        //            }
        //        }
        //        num = i++;
        //    }
        //    return flag;
        //}

        //public static bool SegIntersectPlg(ref VInt2 segSrc, ref VInt2 segVec, VInt2[] plg, out VInt2 nearPoint, out VInt2 projectVec)
        //{
        //    nearPoint = VInt2.zero;
        //    projectVec = VInt2.zero;
        //    if (plg == null || plg.Length < 2)
        //    {
        //        return false;
        //    }
        //    bool result = false;
        //    long num = -1L;
        //    int num2 = -1;
        //    for (int i = 0; i < plg.Length; i++)
        //    {
        //        VInt2 vInt = plg[(i + 1) % plg.Length] - plg[i];
        //        VInt2 vInt2;
        //        if (MathFP.IntersectSegment(ref segSrc, ref segVec, ref plg[i], ref vInt, out vInt2))
        //        {
        //            long sqrMagnitudeLong = (vInt2 - segSrc).sqrMagnitudeLong;
        //            if (num < 0L || sqrMagnitudeLong < num)
        //            {
        //                nearPoint = vInt2;
        //                num = sqrMagnitudeLong;
        //                num2 = i;
        //                result = true;
        //            }
        //        }
        //    }
        //    if (num2 >= 0)
        //    {
        //        VInt2 lhs = plg[(num2 + 1) % plg.Length] - plg[num2];
        //        VInt2 vInt3 = segSrc + segVec - nearPoint;
        //        long num3 = (long)vInt3.x * (long)lhs.x + (long)vInt3.y * (long)lhs.y;
        //        if (num3 < 0L)
        //        {
        //            num3 = -num3;
        //            lhs = -lhs;
        //        }
        //        long sqrMagnitudeLong2 = lhs.sqrMagnitudeLong;
        //        projectVec.x = (int)MathFP.Divide((long)lhs.x * num3, sqrMagnitudeLong2);
        //        projectVec.y = (int)MathFP.Divide((long)lhs.y * num3, sqrMagnitudeLong2);
        //    }
        //    return result;
        //}
    }
}
